/// File: InstallerActions\ProjectInstaller.cs
/// 
/// Author: Antonio Cisternino (cisterni@di.unipi.it)
///   
/// ------------------------------------------------------------
/// Copyright (c) Microsoft Corporation.  All rights reserved.
/// 
/// The use and distribution terms for this software are 
/// contained in the file named license.txt, which can be found 
/// in the root of this distribution.
/// By using this software in any fashion, you are agreeing to 
/// be bound by the terms of this license.
///
/// You must not remove this notice, or any other, from this
/// software.
/// ------------------------------------------------------------


using System;
using System.IO;
using System.Reflection;
using System.Collections;
using System.ComponentModel;
using System.Configuration.Install;
using System.Runtime.InteropServices;
using Microsoft.Win32;

namespace SetupActions
{
    /// <summary>
    /// Flags used in a VSDirEntry.
    /// </summary>
    [Flags]
    public enum VSDirFlags
    {
        None = 0,
        NonLocalTemplate = 1,       // Use non-local user interface behavior and save mechanisms.
        BlankSolution = 2,          // Create a blank (empty) solution; Do not create a project
        DisableBrowseButton = 4,    // Disable the Browse button for this project or item.
        DontAddDefExtension = 8,    // Do not append a default extension to the name provided for the item. (This setting is not valid for projects.)
        DisableLocationField = 32,  // Disable the location field for this project or item.
        DontInitNameField = 4096,   // Do not initialize the name field for this project or item with a valid name.
        DisableNameField = 8192     // Disable the name field for this project or item.
    }

    /// <summary>
    /// Represent an entry of a .vsdir file. For more information see
    /// ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1033/dv_vsintro/html/vxconVSDirFiles.htm
    /// </summary>
    public class VSDirEntry
    {
        public static Guid CSharpPackage = new Guid("{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}");
        public static Guid FSharpPackage = new Guid("{871d2a70-12a2-4e42-9440-425dd92a4116}");

        /// <summary>
        /// Required. The name of the Wizard's .vsz file, such as MyWizard.vsz.
        /// </summary>
        public string RelPathName;

        /// <summary>
        /// Optional. A GUID representing a product (such as Visual C++) that has a
        /// DLL containing localized resources. Normally, this field is blank for
        /// VSDir files that correspond with third-party Wizards.
        /// </summary>
        public Guid Package;

        /// <summary>
        /// Optional. This is the localizable name of the Wizard or template and the
        /// name that appears in the Add Item dialog box. This can be a string or a
        /// resource identifier of the form #ResID.
        /// </summary>
        public string LocalizedName;

        /// <summary>
        /// Required. An integer representing the sort order and relative priority of
        /// the Wizard, with 1 being highest. For instance, if this item is "1," then
        /// this will appear next to other 1s and ahead of all 2s or lower.
        /// </summary>
        public int SortPriority;

        /// <summary>
        /// Required. A localizable description of the template or Wizard as it will
        /// appear in the Add Item dialog box when the item is selected. This can be
        /// a string or a resource identifier of the form #ResID. Applies only to
        /// template files, not folders.
        /// </summary>
        public string Description;

        /// <summary>
        /// Required. Specifies a full path to a DLL or EXE file, or a GUID of a
        /// product that has a .dll file that contains an icon to load for the Wizard.
        /// The icon is loaded as a resource out of a DLL/EXE file using the given
        /// IconResourceId. This setting overrides {clsidPackage}, if specified, for
        /// icon location. Applies only to template files, not folders.
        /// </summary>
        public string DLLPathOrPackageGuid;

        /// <summary>
        /// Optional. A resource identifier within the DLL file that determines the
        /// icon to display. If no icon is defined, the environment substitutes the
        /// default icon for a file with the same extension as the item. Applies only
        /// to template files, not folders.
        /// </summary>
        public int IconResourceId;

        /// <summary>
        /// Required. See Flags description and table below. Applies only to template
        /// files, not folders.
        /// </summary>
        public VSDirFlags Flags;

        /// <summary>
        /// Required. The default name for the Wizard, displayed in the Name field in
        /// the dialog box. This is either a string or a resource identifier of the
        /// form #ResID. If the name is not unique, the environment appends the name
        /// with an integer. For example, MyFile.asp might be changed to MyFile1.asp.
        /// If no name is provided, then "Project" is used. Applies only to template
        /// files, not folders.
        /// </summary>
        public string SuggestedBasename;

        /// <summary>
        /// Create an empty VSDirEntry.
        /// </summary>
        public VSDirEntry()
        {
            RelPathName = null;
            Package = new Guid();
            LocalizedName = null;
            SortPriority = 2;
            Description = null;
            DLLPathOrPackageGuid = null;
            IconResourceId = 0;
            Flags = VSDirFlags.None;
            SuggestedBasename = null;
        }

        /// <summary>
        /// Create a VSDirEntry parsing a line read from the file.
        /// </summary>
        /// <param name="line">Line containing the entry</param>
        public VSDirEntry(string line)
        {
            string[] val = line.Split('|');

            RelPathName = val[0];
            Package = new Guid(val[1]);
            LocalizedName = val[2];
            SortPriority = int.Parse(val[3]);
            Description = val[4];
            DLLPathOrPackageGuid = val[5];
            IconResourceId = int.Parse(val[6]);
            Flags = (VSDirFlags)int.Parse(val[7]);
            SuggestedBasename = val[8];
        }

        /// <summary>
        /// Convert a VSDirEntry into a line to be written in the file.
        /// </summary>
        /// <returns>The entry in file format</returns>
        public override string ToString()
        {
            return string.Format("{0}|{1}|{2}|{3}|{4}|{5}|{6}|{7}|{8}", RelPathName, Package.ToString("B"), LocalizedName,
              SortPriority, Description, DLLPathOrPackageGuid, IconResourceId, (int)Flags, SuggestedBasename);
        }
    }

    /// <summary>
    /// Base class to help developing custom actions for VS installer projects.
    /// In this version you get support for simple configurations, path changes and registry
    /// changes (for multiple configurations).
    /// To write an installer based on this class proceed as follows:
    /// 
    /// - Create a new C# class library
    /// - Set "allow unsafe" flag into the project
    /// - Include this file
    /// - Include the reference to System.Configuration.Install
    /// - Create a class that Inherits from ProjectInstaller
    /// - Label the class with the custom attribute [RunInstaller(true)]
    /// - Add the output of this project to the installer application's folder
    /// - Indicate that the custom actions are defined in the library (now in the Application's folder)
    /// - If you want to read the TargetDir (likely) in the data associated with the Install custom action
    ///   add the /TargetDir="[TARGETDIR]\" string
    /// - If you want to have different configurations add a form with radio buttons to the installer,
    ///   give some name to the radio buttons (i.e. INSTALLTYPE) and integer value to each option (1, 2, ...),
    ///   add the /InstallType="[INSTALLTYPE]" value to the custom data for the Install custom action
    ///   You MUST, however, use /InstallType="0" if no choice is required
    /// - Override the Install method and make it look like the following:
    ///     public override void Install(IDictionary savedState) {
    ///       base.Install(savedState);
    ///
    ///       AddPath(0, TargetDir);
    ///
    ///       SetRegistryValue(2, Registry.LocalMachine, filext, null, "{694DD9B6-B865-4C5B-AD85-86356E9C88DC}");
    ///       SetRegistryValue(2, Registry.LocalMachine, filext, "CanDelete", "True");
    ///     }
    ///     
    ///   Note that the calls inherited from ProjectInstaller allow you to specify the action and
    ///   the install type (0 meand all configurations).
    ///   The Rollback and Uninstall actions are dealt automagically by the class without any additional burden!
    ///   Remember to invoke the base method in the case you override any of the methods!
    ///   
    /// Enjoy!
    /// </summary>
    public class ProjectInstaller : System.Configuration.Install.Installer
    {
        /// <summary>
        /// Needed by the component model.
        /// </summary>
        private System.ComponentModel.Container components = null;

        /// <summary>
        /// Name used to store in the saved state the installation type (an integer)
        /// </summary>
        private const string StateInstallType = "_ProjectInstaller__InstallType";

        /// <summary>
        /// Name used to store in the saved state the modifications made to the path environment variable
        /// </summary>
        private const string PathChanges = "_ProjectInstaller__Path";

        /// <summary>
        /// Name used to store in the saved state the registry keys to be deleted
        /// </summary>
        private const string RegChanges = "_ProjectInstaller__Registry";

        /// <summary>
        /// Name used to store in the saved state the old values of changed keys in the registry.
        /// </summary>
        private const string RegSave = "_ProjectInstaller__RegistrySave";

        /// <summary>
        /// Name used to store in the saved state the list of assemblies registered as COM components.
        /// </summary>
        private const string COMAssemblies = "_ProjectInstaller__COMAssemblies";

        /// <summary>
        /// Name used to store in the saved state the modifications made to vsdir files.
        /// </summary>
        private const string VSDIR = "_ProjectInstaller__VSDIRSave";

        /// <summary>
        /// Name used to store in the saved state installed files.
        /// </summary>
        private const string CopiedFiles = "_ProjectInstaller__CopiedFiles";

        /// <summary>
        /// Cached value for installtype (remember to restore it from the saved state!)
        /// </summary>
        private int installType;

        /// <summary>
        /// Cached value for path to be rmoved from PATH (remember to restore it from the saved state!)
        /// </summary>
        private ArrayList pathAdd;

        /// <summary>
        /// Cached value for Reg keys to be deleted (remember to restore it from the saved state!)
        /// </summary>
        private ArrayList regAdd;

        /// <summary>
        /// Cached value for changes to be restored to the registry (remember to restore it from the saved state!)
        /// </summary>
        private Hashtable regChanges;

        /// <summary>
        /// List of registered COM assemblies
        /// </summary>
        private ArrayList comAssemblies;

        /// <summary>
        /// Cached value for changes to .vsdir files (remember to restore it from the saved state!)
        /// </summary>
        private Hashtable vsdirChanges;

        /// <summary>
        /// List of registered file copied
        /// </summary>
        private ArrayList copiedFiles;

        /// <summary>
        /// Install State (to reduce fan in/out of methods!)
        /// </summary>
        private IDictionary InstallState;

        /// <summary>
        /// Constructor. Initialize installType to 0 (all configurations).
        /// </summary>
        public ProjectInstaller()
            : base()
        {
            InitializeComponent();
            installType = 0;
        }

        /// <summary>
        /// Required by Component model. It does nothing special.
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose(disposing);
        }

        #region Properties

        /// <summary>
        /// Return the InstallType: an integer passed by UI via custom action.
        /// 0 is reserved for "all configurations"
        /// the meaning of all other values is set by the GUI via InstallType
        /// custom data.
        /// </summary>
        protected int InstallType
        {
            get
            {
                return installType;
            }
        }

        /// <summary>
        /// Target dir of the project. Is not null if the TargetDir custom data is 
        /// specified for the Install custom action.
        /// </summary>
        protected string TargetDir
        {
            get
            {
                return Context.Parameters.ContainsKey("TargetDir") ?
                  Context.Parameters["TargetDir"] :
                  null;
            }
        }

        /// <summary>
        /// Return an array of strings of the installed versions of VS.
        /// It is guaranteed that these are the subkeys names used in the
        /// registry.
        /// </summary>
        protected string[] VisualStudioVersions
        {
            get
            {
                using (RegistryKey k = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\VisualStudio"))
                {
                    if (k == null)
                        return null;

                    ArrayList vers = new ArrayList();
                    foreach (string s in k.GetSubKeyNames())
                    {
                        System.Text.RegularExpressions.Regex ver = new System.Text.RegularExpressions.Regex(@"^\d+\.\d+$");
                        if (ver.IsMatch(s))
                            using (RegistryKey vk = k.OpenSubKey(s))
                            {
                                if (vk.GetValue("InstallDir") != null)
                                    vers.Add(s);
                            }
                    }
                    return (string[])vers.ToArray(typeof(string));
                }
            }
        }

        /// <summary>
        /// Return the base dirs of VS installations on the given machine.
        /// </summary>
        protected string[] VisualStudioBaseDirs
        {
            get
            {
                string[] vs = this.VisualStudioVersions;
                if (vs == null)
                    return null;

                string[] ret = new string[vs.Length];
                int i = 0;
                using (RegistryKey vsk = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\VisualStudio"))
                {
                    foreach (string s in vs)
                    {
                        using (RegistryKey k = vsk.OpenSubKey(s))
                        {
                            string p = (string)k.GetValue("InstallDir");
                            if (p.EndsWith("\\"))
                                p = p.Substring(0, p.Length - 1);
                            ret[i++] = Directory.GetParent(p).Parent.FullName;
                        }
                    }
                }
                return ret;
            }
        }

        #endregion

        #region Overridden actions

        /// <summary>
        /// Invoked during install. It adds to the state to be saved all the information
        /// required for undoing actions on PATH environment variable and registry.
        /// </summary>
        /// <param name="savedState">State saved by the installer</param>
        public override void Install(IDictionary savedState)
        {
            base.Install(savedState);
            InstallState = savedState;
            if (Context.Parameters.ContainsKey("InstallType"))
                installType = int.Parse(Context.Parameters["InstallType"]);

            pathAdd = new ArrayList();
            regAdd = new ArrayList();
            regChanges = new Hashtable();
            comAssemblies = new ArrayList();
            vsdirChanges = new Hashtable();
            copiedFiles = new ArrayList();

            savedState.Add(StateInstallType, installType);
            savedState.Add(PathChanges, pathAdd);
            savedState.Add(RegChanges, regAdd);
            savedState.Add(RegSave, regChanges);
            savedState.Add(COMAssemblies, comAssemblies);
            savedState.Add(VSDIR, vsdirChanges);
            savedState.Add(CopiedFiles, copiedFiles);
        }

        /// <summary>
        /// Invoked when rolling back the installation. It cleans up all the action
        /// made by AddPath and SetRegistryValue methods.
        /// </summary>
        /// <param name="savedState">State saved by the installer</param>
        public override void Rollback(IDictionary savedState)
        {
            installType = (int)savedState[StateInstallType];
            pathAdd = (ArrayList)savedState[PathChanges];
            regAdd = (ArrayList)savedState[RegChanges];
            regChanges = (Hashtable)savedState[RegSave];
            comAssemblies = (ArrayList)savedState[COMAssemblies];
            vsdirChanges = (Hashtable)savedState[VSDIR];
            copiedFiles = (ArrayList)savedState[CopiedFiles];

            CleanUp();

            base.Rollback(savedState);
        }

        /// <summary>
        /// Invoked during uninstall. It cleans up all the action
        /// made by AddPath and SetRegistryValue methods.
        /// </summary>
        /// <param name="savedState">State saved by the installer</param>
        public override void Uninstall(IDictionary savedState)
        {
            installType = (int)savedState[StateInstallType];
            pathAdd = (ArrayList)savedState[PathChanges];
            regAdd = (ArrayList)savedState[RegChanges];
            regChanges = (Hashtable)savedState[RegSave];
            comAssemblies = (ArrayList)savedState[COMAssemblies];
            vsdirChanges = (Hashtable)savedState[VSDIR];
            copiedFiles = (ArrayList)savedState[CopiedFiles];

            CleanUp();

            base.Uninstall(savedState);
        }
        #endregion

        #region Auto undoable actions

        /// <summary>
        /// Add a string to the PATH environment variable. The modification is made if PATH
        /// doesn't already contain the value and if the installation type is either 0 or
        /// matches the current installation type.
        /// </summary>
        /// <param name="instType">Installation type for which the action is intended</param>
        /// <param name="Mpath">Path to add</param>
        protected void AddPath(int instType, string Mpath)
        {
            if (instType != 0 && installType != instType)
                return;

            try
            {
                pathAdd.Add(Mpath);
                RegistryKey env = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Control\Session Manager\Environment");
                string path = (string)env.GetValue("Path", "");
                env.Close();

                if (path.IndexOf(Mpath) < 0)
                {
                    path = path + ";" + Mpath;
                    env = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Control\Session Manager\Environment", true);
                    env.SetValue("Path", path);
                    env.Close();
                    int i;
                    // 0xffff is HWND_BROADCAST (WinUser.h), 0x001A is WM_SETTINGCHANGE (WinUser.h), 0x0002 is SMTO_ABORTIFHUNG (WinUser.h)
                    SendMessageTimeout(0xffff, 0x001A, 0, 0, 0x0002, 1000, out i);
                }
            }
            catch (InstallException iE)
            {
                System.Console.WriteLine(iE.ToString());
            }
        }

        /// <summary>
        /// Set a registry value, creating the path if necessary. Old values are baked up and restored during
        /// uninstall.
        /// </summary>
        /// <param name="instType">Installation type for which the action is intended</param>
        /// <param name="root">Registry key to used as root</param>
        /// <param name="key">Subkey name</param>
        /// <param name="keyname">Name of the value</param>
        /// <param name="val">Value</param>
        protected void SetRegistryValue(int instType, RegistryKey root, string key, string keyname, object val)
        {
            if (instType != 0 && installType != instType)
                return;

            RegistryKey subkey = root.OpenSubKey(key, true);
            if (subkey == null)
            {
                string[] keys = key.Split('\\');
                subkey = root;
                for (int i = 0; i < keys.Length; i++)
                {
                    if (subkey.OpenSubKey(keys[i]) == null)
                    {
                        string k = string.Join("\\", keys, i, keys.Length - i);
                        subkey = subkey.CreateSubKey(k);
                        k = string.Format("{0}\\{1}", root.Name, string.Join("\\", keys, 0, i + 1));

                        foreach (string s in regAdd)
                            if (k.StartsWith(s))
                            {
                                k = null;
                                break;
                            }

                        if (k != null)
                            regAdd.Add(k);
                        break;
                    }
                    else
                        subkey = subkey.OpenSubKey(keys[i], true);
                }
            }

            bool saveValue = true;
            foreach (string s in regAdd)
            {
                if (subkey.Name.StartsWith(s))
                {
                    saveValue = false;
                    break;
                }
            }

            if (saveValue && subkey.GetValue(keyname) != null)
                regChanges[subkey.Name + @"\\" + (keyname == null ? "" : keyname)] = subkey.GetValue(keyname);

            subkey.SetValue(keyname, val);
        }

        /// <summary>
        /// Register a .NET assembly as a COM component.
        /// See ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1033/cpguide/html/cpconRegisteringAssembliesWithCOM.htm
        /// for more information.
        /// </summary>
        /// <param name="instType">Type of installation</param>
        /// <param name="path">Path to the assembly to be registered</param>
        /// <param name="codebase">If true creates a InProcServer32 key with the path to the assembly. Beware that this
        /// can be specified only if the assembly has a strong name.</param>
        protected void RegisterAssemblyWithCOM(int instType, string path, bool codebase)
        {
            if (instType != 0 && installType != instType)
                return;

            Assembly a = Assembly.LoadFrom(path);

            RegistrationServices reg = new RegistrationServices();
            reg.RegisterAssembly(a, codebase ? AssemblyRegistrationFlags.SetCodeBase : AssemblyRegistrationFlags.None);

            comAssemblies.Add(path);
        }

        /// <summary>
        /// Add an entry to a .VSDir file.
        /// </summary>
        /// <param name="instType">Type of installation</param>
        /// <param name="path">Path to the file to modify</param>
        /// <param name="entry">Entry to add</param>
        protected void AddVsdirEntry(int instType, string path, VSDirEntry entry)
        {
            if (instType != 0 && installType != instType)
                return;

            string cont = entry.ToString();

            // This is used to have multiple entries in the same file
            vsdirChanges[vsdirChanges.Count + "-" + path] = cont;

            StreamWriter o = new StreamWriter(path, true);
            o.WriteLine(cont);
            o.Close();
        }

        /// <summary>
        /// Copy a file to a specific location. This method has been introduced
        /// to support installation of files whose location depends on the machine.
        /// </summary>
        /// <param name="instType">Type of installation</param>
        /// <param name="file">Path to the file to be copied, usually relaltive
        /// to the installation directory.</param>
        /// <param name="targetDir">Directory target for the file.</param>
        protected void CopyFile(int instType, string file, string targetDir)
        {
            if (instType != 0 && installType != instType)
                return;

            MakeDir(instType, targetDir);

            string fname = new FileInfo(file).Name;
            File.Copy(file, targetDir + "\\" + fname, true);
            copiedFiles.Add(targetDir + "\\" + fname);
        }

        /// <summary>
        /// Move a file to a specific location. This method has been introduced
        /// to support installation of files whose location depends on the machine.
        /// </summary>
        /// <param name="instType">Type of installation</param>
        /// <param name="file">Path to the file to be moved, usually relaltive
        /// to the installation directory.</param>
        /// <param name="targetDir">Directory target for the file.</param>
        protected void MoveFile(int instType, string file, string targetDir)
        {
            if (instType != 0 && installType != instType)
                return;

            MakeDir(instType, targetDir);

            string fname = new FileInfo(file).Name;
            File.Move(file, targetDir + "\\" + fname);
            copiedFiles.Add(targetDir + "\\" + fname);
        }

        /// <summary>
        /// Create a Directory. This method has been introduced
        /// to support installation of files whose location depends on the machine.
        /// </summary>
        /// <param name="instType">Type of installation</param>
        /// to the installation directory.</param>
        /// <param name="targetDir">Directory to create.</param>
        protected void MakeDir(int instType, string targetDir)
        {
            if (instType != 0 && installType != instType)
                return;

            if (!Directory.Exists(targetDir))
            {
                DirectoryInfo d = new DirectoryInfo(targetDir);
                while (!d.Parent.Exists) d = d.Parent;

                Directory.CreateDirectory(targetDir);
                copiedFiles.Add(d.FullName);
            }
        }

        /// <summary>
        /// Copy a file to a specific location. This method has been introduced
        /// to support installation of files whose location depends on the machine.
        /// </summary>
        /// <param name="instType">Type of installation</param>
        /// <param name="file">Source file</param>
        /// <param name="targetfile">Copied file</param>
        protected void RenameFile(int instType, string file, string targetfile, bool copy)
        {
            if (instType != 0 && installType != instType)
                return;
            if (copy)
                File.Copy(file, targetfile, true);
            else
                File.Move(file, targetfile);
            copiedFiles.Add(targetfile);
        }

        /// <summary>
        /// Simply register a file for removal. Useful for install-time generated files.
        /// </summary>
        /// <param name="instType"></param>
        /// <param name="file"></param>
        protected void RegisterFileOrDirectory(int instType, string file)
        {
            if (instType != 0 && installType != instType)
                return;
            copiedFiles.Add(file);
        }

        #endregion

        /// <summary>
        /// Return the base installation dir of a specific version of Visual Studio.
        /// </summary>
        /// <param name="version">Version requested.</param>
        /// <returns>The path to the base dir or null if the requested version is not present.</returns>
        protected string GetVisualStudioBaseDir(string version)
        {
            string[] v = this.VisualStudioVersions;
            int i = Array.IndexOf(v, version);
            return (i == -1) ? null : this.VisualStudioBaseDirs[i];
        }

        /// <summary>
        /// Remove the specified directory from path (if it exists).
        /// </summary>
        /// <param name="Mpath">Directory to be removed.</param>
        protected void RemovePath(string Mpath)
        {
            Mpath = ";" + Mpath;
            try
            {
                RegistryKey env = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Control\Session Manager\Environment");
                string path = (string)env.GetValue("Path", "");
                env.Close();

                int index = path.IndexOf(Mpath);
                if (index > 0)
                {
                    string a = path.Substring(0, index);
                    if (index + Mpath.Length < path.Length)
                    {
                        string b = path.Substring(index + Mpath.Length, path.Length - a.Length - Mpath.Length);
                        path = a + b;
                    }
                    else
                    {
                        path = a;
                    }

                    env = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Control\Session Manager\Environment", true);
                    env.SetValue("Path", path);
                    env.Close();
                    int i;
                    // 0xffff is HWND_BROADCAST (WinUser.h), 0x001A is WM_SETTINGCHANGE (WinUser.h), 0x0002 is SMTO_ABORTIFHUNG (WinUser.h)
                    SendMessageTimeout(0xffff, 0x001A, 0, 0, 0x0002, 1000, out i);
                }
            }
            catch (InstallException iE)
            {
                System.Console.WriteLine(iE.ToString());
            }
        }

        /// <summary>
        /// Unregister the assembly with COM.
        /// </summary>
        /// <param name="path">Path to the assembly to unregister</param>
        protected void UnregisterAssemblyWithCOM(string path)
        {
            Assembly a = Assembly.LoadFrom(path);

            RegistrationServices reg = new RegistrationServices();
            reg.UnregisterAssembly(a);
        }

        /// <summary>
        /// Remove an entry from a VSDir file.
        /// </summary>
        /// <param name="path">Path of the file .vsdir to change</param>
        /// <param name="line">Line to omit</param>
        protected void RemoveVSDirEntry(string path, string line)
        {
            StreamReader r = File.OpenText(path);
            StreamWriter w = new StreamWriter(path + ".tmp", true);
            string l = null;
            while ((l = r.ReadLine()) != null)
            {
                if (line != l)
                    w.WriteLine(l);
            }
            w.Close();
            r.Close();
            File.Delete(path);
            File.Move(path + ".tmp", path);
        }

        /// <summary>
        /// Used to find the appropriate hive given a registry key name.
        /// </summary>
        /// <param name="key">Hive containing the key</param>
        /// <param name="path">Full path is changed into the path relative to the hive</param>
        private void SplitKey(out RegistryKey key, ref string path)
        {
            // Find the root (I suspect some of the following are useless...)
            if (path.StartsWith(Registry.ClassesRoot.Name))
            {
                key = Registry.ClassesRoot;
                path = path.Substring(key.Name.Length + 1); // +1 due to the \ char!
            }
            else if (path.StartsWith(Registry.CurrentConfig.Name))
            {
                key = Registry.CurrentConfig;
                path = path.Substring(key.Name.Length + 1); // +1 due to the \ char!
            }
            else if (path.StartsWith(Registry.CurrentUser.Name))
            {
                key = Registry.CurrentUser;
                path = path.Substring(key.Name.Length + 1); // +1 due to the \ char!
            }
            else if (path.StartsWith(Registry.DynData.Name))
            {
                key = Registry.DynData;
                path = path.Substring(key.Name.Length + 1); // +1 due to the \ char!
            }
            else if (path.StartsWith(Registry.LocalMachine.Name))
            {
                key = Registry.LocalMachine;
                path = path.Substring(key.Name.Length + 1); // +1 due to the \ char!
            }
            else if (path.StartsWith(Registry.PerformanceData.Name))
            {
                key = Registry.PerformanceData;
                path = path.Substring(key.Name.Length + 1); // +1 due to the \ char!
            }
            else if (path.StartsWith(Registry.Users.Name))
            {
                key = Registry.Users;
                path = path.Substring(key.Name.Length + 1); // +1 due to the \ char!
            }
            else
                key = null;
        }

        /// <summary>
        /// Undo all the actions specified in the various lists.
        /// </summary>
        private void CleanUp()
        {
            foreach (string path in pathAdd)
                RemovePath(path);
            pathAdd.Clear();

            RegistryKey key = null;

            foreach (string s in regAdd)
                try
                {
                    string path = s;
                    SplitKey(out key, ref path);
                    key.DeleteSubKeyTree(path);
                }
                catch
                {
                    System.Diagnostics.EventLog.WriteEntry("VSLab uninstaller", "Failed removing " + s + " registry entry", System.Diagnostics.EventLogEntryType.Warning, 101);
                }
            regAdd.Clear();

            foreach (string s in regChanges.Keys)
            {
                string path = s;
                int idx = path.LastIndexOf(@"\\"); // There should be!
                string name = path.Substring(idx + 2);
                path = path.Substring(0, idx);
                SplitKey(out key, ref path);
                key = key.OpenSubKey(path, true);
                key.SetValue(name, regChanges[s]);
            }
            regChanges.Clear();

            foreach (string path in comAssemblies)
                UnregisterAssemblyWithCOM(path);
            comAssemblies.Clear();

            foreach (string s in vsdirChanges.Keys)
            {
                string path = s;
                string val = (string)vsdirChanges[s];
                // By definition there is a unique prefix
                path = path.Substring(path.IndexOf("-") + 1);
                RemoveVSDirEntry(path, val);
            }
            vsdirChanges.Clear();

            foreach (string n in copiedFiles)
            {
                if (File.Exists(n))
                    File.Delete(n);
                else if (Directory.Exists(n))
                    Directory.Delete(n, true);
            }
            copiedFiles.Clear();
        }

        #region Component Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {

        }
        #endregion

        /// <summary>
        /// Required to notify the PATH change.
        /// </summary>
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern int SendMessageTimeout(
          int hWnd,
          uint Msg,
          short wParam,
          short lParam,
          uint fuFlags,
          uint uTimeout,
          out int lpdwResult);
    }
}
